﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ObjectComposition
{
    public interface IBinding
    {
        void OnChanged(IProperty changed);
    }

    public class Binding<Type, Type2> : IBinding
    {
        protected Property<Type> destination;
        protected Func<Type> get;
        protected IProperty[] sources;

        protected Binding()
        {
        }

        public Binding(Property<Type> _destination, Func<Type2, Type> transform, Property<Type2> _source)
        {
            this.destination = _destination;
            _source.AddBinding(this);
            this.get = () => transform(_source.Value);
            this.sources = new IProperty[] { _source };
            this.OnChanged(_source);
        }

        public virtual void OnChanged(IProperty changed)
        {
            this.destination.InternalSet(this.get(), this);
        }

        public virtual void Delete()
        {
            foreach (IProperty property in this.sources)
                property.RemoveBinding(this);
        }
    }

    public class Binding<Type> : Binding<Type, Type>
    {
        public Binding(Property<Type> _destination, Property<Type> _source)
            : base(_destination, x => x, _source)
        {

        }

        public Binding(Property<Type> _destination, Func<Type> _get, params IProperty[] _sources)
        {
            this.destination = _destination;
            this.get = _get;
            this.sources = _sources;
            foreach (IProperty property in this.sources)
                property.AddBinding(this);
            this.OnChanged(_sources[0]);
        }
    }

    public class ReevaluateBinding<Type, Type2> : Binding<Type, Type2>
    {
        protected ReevaluateBinding()
        {
        }

        public ReevaluateBinding(Property<Type> _destination, Func<Type2, Type> transform, Property<Type2> _source)
        {
            this.destination = _destination;
            _source.AddBinding(this);
            _destination.AddReevaluateBinding(this);
            this.get = () => transform(_source.Value);
            this.sources = new IProperty[] { _source };
            this.OnChanged(_source);
        }

        public override void Delete()
        {
            foreach (IProperty property in this.sources)
                property.RemoveBinding(this);
            this.destination.RemoveReevaluateBinding(this);
        }
    }

    public class ReevaluateBinding<Type> : ReevaluateBinding<Type, Type>
    {
        public ReevaluateBinding(Property<Type> _destination, Property<Type> _source)
            : base(_destination, x => x, _source)
        {

        }

        public ReevaluateBinding(Property<Type> _destination, Func<Type> _get, params IProperty[] _sources)
        {
            this.destination = _destination;
            this.get = _get;
            this.sources = _sources;
            foreach (IProperty property in this.sources)
                property.AddBinding(this);
            _destination.AddReevaluateBinding(this);
            this.OnChanged(_sources[0]);
        }
    }

    /// <summary>
    /// Important: When initializing, the first given property takes precedence.
    /// </summary>
    /// <typeparam name="Type"></typeparam>
    /// <typeparam name="Type2"></typeparam>
    public class TwoWayBinding<Type, Type2> : Binding<Type, Type2>
    {
        protected Property<Type> property1;
        protected IProperty[] property1Sources;
        protected Property<Type2> property2;
        protected IProperty[] property2Sources;
        protected Func<Type2, Type> transform1;
        protected Func<Type, Type2> transform2;

        public TwoWayBinding(
            Property<Type> _property1,
            Func<Type2, Type> _transform1,
            Property<Type2> _property2,
            Func<Type, Type2> _transform2)
            : this(_property1, _transform1, new IProperty[] { }, _property2, _transform2, new IProperty[] { })
        {
        }

        public TwoWayBinding(
            Property<Type> _property1,
            Func<Type2, Type> _transform1,
            IEnumerable<IProperty> _property1Sources,
            Property<Type2> _property2,
            Func<Type, Type2> _transform2,
            IEnumerable<IProperty> _property2Sources)
        {
            this.property1 = _property1;
            this.property2 = _property2;
            this.property1Sources = _property1Sources.Union(new IProperty[] { this.property2 }).ToArray();
            this.property2Sources = _property2Sources.Union(new IProperty[] { this.property1 }).ToArray();
            this.transform1 = _transform1;
            this.transform2 = _transform2;
            foreach (IProperty property in this.property1Sources)
                property.AddBinding(this);
            foreach (IProperty property in this.property2Sources)
                property.AddBinding(this);
            this.OnChanged(this.property1);
        }

        public override void OnChanged(IProperty changed)
        {
            if (this.property2Sources.Contains(changed))
                this.property2.InternalSet(this.transform2(this.property1.Value), this);
            else if (this.property1Sources.Contains(changed))
                this.property1.InternalSet(this.transform1(this.property2.Value), this);
            else
                throw new Exception("Binding received improper property change notification.");
        }
    }

    /// <summary>
    /// Important: When initializing, the first given property takes precedence.
    /// </summary>
    /// <typeparam name="Type"></typeparam>
    public class TwoWayBinding<Type> : TwoWayBinding<Type, Type>
    {
        public TwoWayBinding(Property<Type> _property1, Property<Type> _property2)
            : base(_property1, x => x, _property2, x => x)
        {
        }
    }
}
